/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package modules.enderecamento.models.dao;

/**
 *
 * @author Clezio
 */
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import modules.enderecamento.exception.EspacoExcedidoPaleteException;
import modules.enderecamento.exception.EspacoExcedidoRuaException;
import modules.enderecamento.exception.ReincidenciaException;
import modules.enderecamento.exception.SaldoInsuficienteException;
import modules.enderecamento.models.dto.Enderecamento;
import modules.enderecamento.models.dto.Entrada;
import modules.enderecamento.models.dto.Palete;
import modules.enderecamento.models.dto.Produto;
import modules.enderecamento.models.dto.Rua;
import modules.enderecamento.models.dto.Saida;
import modules.enderecamento.util.BoxPanel;
import modules.enderecamento.util.HibernateUtil;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;

public class EnderecamentoDAO {

    private SessionFactory sessionFactory;

    public EnderecamentoDAO() throws Exception {
        sessionFactory = HibernateUtil.getSessionFactory();
    }

    public void registrarEntrada(Entrada entrada, Boolean ignoreEspaco) throws Exception {
        Enderecamento enderecamento = new Enderecamento();
        enderecamento.setPalete(entrada.getPalete());
        enderecamento.setProduto(entrada.getProduto());
        enderecamento.setQuantidade(entrada.getQuantidade());

        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        //Checa espaço no palete
        List<Enderecamento> listEnderecamento = session.createCriteria(Enderecamento.class).add(Restrictions.eq("palete.id", enderecamento.getPalete().getId())).list();
        double cubagemOcupada = 0;
        for (int i = 0; i < listEnderecamento.size(); i++) {
            Produto produto = listEnderecamento.get(i).getProduto();
            int qtde = listEnderecamento.get(i).getQuantidade();
            BigDecimal produtoAltura = produto.getAltura().divide(BigDecimal.valueOf(100));
            BigDecimal produtoComprimento = produto.getComprimento().divide(BigDecimal.valueOf(100));
            BigDecimal produtoLargura = produto.getLargura().divide(BigDecimal.valueOf(100));
            BigDecimal subTotal = (produtoAltura.multiply(produtoComprimento).multiply(produtoLargura));
            cubagemOcupada += subTotal.doubleValue() * qtde;
        }
        //Checa espaço que será ocupado
        Produto produto = enderecamento.getProduto();
        BigDecimal produtoAltura = produto.getAltura().divide(BigDecimal.valueOf(100));
        BigDecimal produtoComprimento = produto.getComprimento().divide(BigDecimal.valueOf(100));
        BigDecimal produtoLargura = produto.getLargura().divide(BigDecimal.valueOf(100));
        BigDecimal subTotal = (produtoAltura.multiply(produtoComprimento).multiply(produtoLargura));
        int qtde = enderecamento.getQuantidade();
        cubagemOcupada += subTotal.doubleValue() * qtde;

        if (cubagemOcupada > enderecamento.getPalete().getCubagem() && !ignoreEspaco) {
            tx.rollback();
            session.close();
            throw new EspacoExcedidoPaleteException("Não há espaço físico no Palete informado");
        } else {
            Enderecamento obj = (Enderecamento) session.createCriteria(Enderecamento.class).add(Restrictions.eq("produto.id", enderecamento.getProduto().getId())).add(Restrictions.eq("palete.id", enderecamento.getPalete().getId())).uniqueResult();
            if (obj != null) {
                int novaQuantidade = obj.getQuantidade() + enderecamento.getQuantidade();
                enderecamento = obj;
                enderecamento.setQuantidade(novaQuantidade);
            }

            Integer qtdePaletes = new RuaDAO().obterQtdePaleteByRua(enderecamento.getPalete().getRua());
//            Palete palete = new PaleteDAO().retrieve(enderecamento.getPalete().getId());
            Boolean checkPalete = checkPaleteByRua(enderecamento.getPalete().getId(), enderecamento.getPalete().getRua().getId());
            Integer qtdeMaximo = enderecamento.getPalete().getRua().getQuantidadePaletes();
            if ((qtdePaletes > qtdeMaximo && checkPalete) || (qtdePaletes >= qtdeMaximo && checkPalete == false)) {
                tx.rollback();
                session.close();
                throw new EspacoExcedidoRuaException("A quantidade de paletes excede a quantidade máxima permitida para esta Rua.");
            } else {
                //Atualizar rua do Palete
                session.update(enderecamento.getPalete());
                //Registrar entrada
                session.save(entrada);
                //Atualizar ou registrar endereçamento
                if (enderecamento.getId() > 0) {
                    session.update(enderecamento);
                } else {
                    session.save(enderecamento);
                }
            }
        }
        session.flush();
        tx.commit();
        session.close();
    }

    public java.util.List getList(String condicao) throws Exception {
        Session session = sessionFactory.openSession();
        Query query = session.createQuery(condicao);
        List<Enderecamento> enderecamentosTmp = query.list();
        List<Enderecamento> enderecamento = new ArrayList();
        for (int i = 0; i < enderecamentosTmp.size(); i++) {
            enderecamento.add(mountDto(enderecamentosTmp.get(i)));
        }
        session.flush();
        session.close();
        return enderecamento;
    }

    public Enderecamento retrieve(Integer pk) throws Exception {
        Session session = sessionFactory.openSession();
        Enderecamento enderecamento = (Enderecamento) session.load(Enderecamento.class, pk);
        Enderecamento enderecamentoDTO = mountDto(enderecamento);
        session.flush();
        session.close();
        return enderecamentoDTO;
    }

    public void registrarSaida(Saida saida) throws Exception {
        Enderecamento enderecamento = new Enderecamento();
        enderecamento.setPalete(saida.getPalete());
        enderecamento.setProduto(saida.getProduto());
        enderecamento.setQuantidade(saida.getQuantidade());

        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();

        Enderecamento enderecamentoBanco = (Enderecamento) session.createCriteria(Enderecamento.class).add(Restrictions.eq("produto.id", enderecamento.getProduto().getId())).add(Restrictions.eq("palete.id", enderecamento.getPalete().getId())).uniqueResult();
        if (enderecamentoBanco != null) {
            Integer novaQuantidade = enderecamentoBanco.getQuantidade() - enderecamento.getQuantidade();
            session.save(saida);
            if (novaQuantidade < 0) {
                tx.rollback();
                session.close();
                throw new SaldoInsuficienteException("Saldo insuficiente");
            } else if (novaQuantidade == 0) {
                session.delete(enderecamentoBanco);
            } else {
                enderecamentoBanco.setQuantidade(novaQuantidade);
                session.save(enderecamentoBanco);
            }
        }
        session.flush();
        tx.commit();
        session.close();
    }

    private Enderecamento mountDto(Enderecamento enderecamento) throws Exception {
        Enderecamento enderecamentoDTO = new Enderecamento();
        enderecamentoDTO.setId(enderecamento.getId());
        enderecamentoDTO.setProduto(new ProdutoDAO().mountDto(enderecamento.getProduto()));
        enderecamentoDTO.setPalete(new PaleteDAO().mountDto(enderecamento.getPalete()));
        enderecamentoDTO.setQuantidade(enderecamento.getQuantidade());
        return enderecamentoDTO;
    }

    public Enderecamento obterEnderecamentoByPaleteProduto(Enderecamento enderecamento) throws Exception {
        Session session = sessionFactory.openSession();
        Enderecamento obj = (Enderecamento) session.createCriteria(Enderecamento.class).add(Restrictions.eq("produto.id", enderecamento.getProduto().getId())).add(Restrictions.eq("palete.id", enderecamento.getPalete().getId())).uniqueResult();
        Enderecamento enderecamentoDTO = null;
        if (obj != null) {
            enderecamentoDTO = mountDto(obj);
        }
        session.flush();
        session.close();
        return enderecamentoDTO;
    }

    public List<Enderecamento> getListByPalete(Palete palete) throws Exception {
        Session session = sessionFactory.openSession();
        List<Enderecamento> enderecamentosTmp = session.createCriteria(Enderecamento.class).add(Restrictions.eq("palete.id", palete.getId())).addOrder(Order.asc("produto")).list();
        List<Enderecamento> listEnderecamento = new ArrayList();
        for (int i = 0; i < enderecamentosTmp.size(); i++) {
            listEnderecamento.add(mountDto(enderecamentosTmp.get(i)));
        }
        session.flush();
        session.close();
        return listEnderecamento;
    }

    public boolean checkProdutoPalete(Produto produto, Palete palete) {
        Session session = sessionFactory.openSession();
        Enderecamento enderecamento = (Enderecamento) session.createCriteria(Enderecamento.class).add(Restrictions.eq("produto.id", produto.getId())).add(Restrictions.eq("palete.id", palete.getId())).uniqueResult();
        session.flush();
        session.close();
        if (enderecamento != null) {
            return true;
        }
        return false;
    }

    public boolean checkPaleteByRua(Integer paleteId, Integer ruaId) {
        Session session = sessionFactory.openSession();
        Palete palete = (Palete) session.createCriteria(Palete.class).add(Restrictions.eq("id", paleteId)).add(Restrictions.eq("rua.id", ruaId)).uniqueResult();
        session.flush();
        session.close();
        if (palete != null) {
            return true;
        }
        return false;
    }

    public void removeProdutosPalete(Integer paleteId) {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        List<Enderecamento> enderecamentosTmp = session.createCriteria(Enderecamento.class).add(Restrictions.eq("palete.id", paleteId)).list();
        for (int i = 0; i < enderecamentosTmp.size(); i++) {
            session.delete(enderecamentosTmp.get(i));
        }

        session.flush();
        tx.commit();
        session.close();

    }

    public void removeProdutosRua(String ruaName) {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        Query query = session.createQuery("from Enderecamento where palete.rua.nome = '" + ruaName + "'");
        List<Enderecamento> enderecamentosTmp = query.list();
        for (int i = 0; i < enderecamentosTmp.size(); i++) {
            session.delete(enderecamentosTmp.get(i));
        }

        session.flush();
        tx.commit();
        session.close();
    }

    public void moverProdutoPalete(Palete paleteOrigem, Palete paleteDestino, Produto produtoParametro, Integer quantidade) throws EspacoExcedidoPaleteException, EspacoExcedidoRuaException, SaldoInsuficienteException, Exception {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();

        //Remover saldo do palete origem
        Enderecamento enderecamentoOrigemBanco = (Enderecamento) session.createCriteria(Enderecamento.class).add(Restrictions.eq("produto.id", produtoParametro.getId())).add(Restrictions.eq("palete.id", paleteOrigem.getId())).uniqueResult();
        if (enderecamentoOrigemBanco != null) {
            Integer novaQuantidade = enderecamentoOrigemBanco.getQuantidade() - quantidade;
            if (novaQuantidade < 0) {
                tx.rollback();
                session.close();
                throw new SaldoInsuficienteException("Saldo insuficiente para transferência");
            } else if (novaQuantidade == 0) {
                session.delete(enderecamentoOrigemBanco);
            } else {
                enderecamentoOrigemBanco.setQuantidade(novaQuantidade);
                session.save(enderecamentoOrigemBanco);
            }
        }

        //Checa espaço no palete
        List<Enderecamento> listEnderecamento = session.createCriteria(Enderecamento.class).add(Restrictions.eq("palete.id", paleteDestino.getId())).list();
        double cubagemOcupada = 0;
        for (int i = 0; i < listEnderecamento.size(); i++) {
            Produto produto = listEnderecamento.get(i).getProduto();
            int qtde = listEnderecamento.get(i).getQuantidade();
            BigDecimal produtoAltura = produto.getAltura().divide(BigDecimal.valueOf(100));
            BigDecimal produtoComprimento = produto.getComprimento().divide(BigDecimal.valueOf(100));
            BigDecimal produtoLargura = produto.getLargura().divide(BigDecimal.valueOf(100));
            BigDecimal subTotal = (produtoAltura.multiply(produtoComprimento).multiply(produtoLargura));
            cubagemOcupada += subTotal.doubleValue() * qtde;
        }
        //Checa espaço que será ocupado
        Produto produto = produtoParametro;
        BigDecimal produtoAltura = produto.getAltura().divide(BigDecimal.valueOf(100));
        BigDecimal produtoComprimento = produto.getComprimento().divide(BigDecimal.valueOf(100));
        BigDecimal produtoLargura = produto.getLargura().divide(BigDecimal.valueOf(100));
        BigDecimal subTotal = (produtoAltura.multiply(produtoComprimento).multiply(produtoLargura));
        cubagemOcupada += subTotal.doubleValue() * quantidade;

        if (cubagemOcupada > paleteDestino.getCubagem()) {
            tx.rollback();
            session.close();
            throw new EspacoExcedidoPaleteException("Não há espaço físico no Palete informado");
        } else {
            Enderecamento obj = (Enderecamento) session.createCriteria(Enderecamento.class).add(Restrictions.eq("produto.id", produtoParametro.getId())).add(Restrictions.eq("palete.id", paleteDestino.getId())).uniqueResult();
            Enderecamento enderecamento = new Enderecamento();
            if (obj != null) {
                int novaQuantidade = obj.getQuantidade() + quantidade;
                enderecamento = obj;
                enderecamento.setQuantidade(novaQuantidade);
            } else {
                enderecamento.setPalete(paleteDestino);
                enderecamento.setProduto(produtoParametro);
                enderecamento.setQuantidade(quantidade);
            }

            Integer qtdePaletes = new RuaDAO().obterQtdePaleteByRua(paleteDestino.getRua());
            Boolean checkPalete = checkPaleteByRua(paleteDestino.getId(), paleteDestino.getRua().getId());
            Integer qtdeMaximo = paleteDestino.getRua().getQuantidadePaletes();
            if ((qtdePaletes > qtdeMaximo && checkPalete) || (qtdePaletes >= qtdeMaximo && checkPalete == false)) {
                tx.rollback();
                session.close();
                throw new EspacoExcedidoRuaException("A quantidade de paletes excede a quantidade máxima permitida para esta Rua.");
            } else {
                session.update(paleteDestino);
                if (enderecamento.getId() > 0) {
                    session.update(enderecamento);
                } else {
                    session.save(enderecamento);
                }
            }
        }
        session.flush();
        tx.commit();
        session.close();
    }

    public void moverPaleteRua(Rua ruaOrigem, Rua ruaDestino, Palete palete) throws EspacoExcedidoRuaException, Exception {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();

        Integer qtdePaletes = new RuaDAO().obterQtdePaleteByRua(ruaDestino);
        Integer qtdeMaximo = ruaDestino.getQuantidadePaletes();
        if (qtdePaletes >= qtdeMaximo) {
            tx.rollback();
            session.close();
            throw new EspacoExcedidoRuaException("A quantidade de paletes excede a quantidade máxima permitida para esta Rua.");
        } else {
            palete.setRua(ruaDestino);
            session.update(palete);
        }
        session.flush();
        tx.commit();
        session.close();
    }

    public List<Entrada> listarEntradasByData(Date date) {
        Session session = sessionFactory.openSession();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        StringBuilder query = new StringBuilder("from Entrada ");
        query.append(" where data_entrada = '" + sdf.format(date) + "'");
        query.append(" order by id");
        Query result = session.createQuery(query.toString());
        List<Entrada> registrosTmp = result.list();
        List<Entrada> registros = new ArrayList(registrosTmp);
        session.flush();
        session.close();
        return registros;
    }

    public Entrada obterEntradaById(Integer id) {
        Session session = sessionFactory.openSession();
        Entrada entrada = (Entrada) session.load(Entrada.class, id);
        Entrada entradaDto = mountDtoEntrada(entrada);
        session.flush();
        session.close();
        return entradaDto;
    }

    public void cancelarEntrada(Entrada entrada) throws ReincidenciaException,SaldoInsuficienteException {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        entrada = (Entrada) session.load(Entrada.class, entrada.getId());
        if(entrada.isCancelado()){
            session.close();
            throw new ReincidenciaException("Registro já cancelado anteriormente");
        }
        entrada.setCancelado(true);
        Enderecamento enderecamento = (Enderecamento) session.createCriteria(Enderecamento.class).add(Restrictions.eq("produto.id", entrada.getProduto().getId())).add(Restrictions.eq("palete.id", entrada.getPalete().getId())).uniqueResult();
        if (enderecamento != null) {
            Integer novaQuantidade = enderecamento.getQuantidade() - entrada.getQuantidade();
            if (novaQuantidade < 0) {
                tx.rollback();
                session.close();
                throw new SaldoInsuficienteException("Saldo insuficiente no endereço destino.");
            } else if (novaQuantidade == 0) {
                session.delete(enderecamento);
            } else {
                enderecamento.setQuantidade(novaQuantidade);
                session.save(enderecamento);
            }
        }else{
            tx.rollback();
            session.close();
            throw new SaldoInsuficienteException("Saldo insuficiente no endereço destino.");
        }
        session.update(entrada);
        session.flush();
        tx.commit();
        session.close();
    }

    private Entrada mountDtoEntrada(Entrada entrada){
        return new Entrada(entrada.getId(),
                entrada.getUsuario(),
                entrada.getTiposMovimento(),
                entrada.getRua(),
                entrada.getProduto(),
                entrada.getPalete(),
                entrada.getQuantidade(),
                null,
                entrada.getDataEntrada(),
                entrada.isCancelado());
    }

    public List<Saida> listarSaidasByData(Date date) {
        Session session = sessionFactory.openSession();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        StringBuilder query = new StringBuilder("from Saida ");
        query.append(" where data_saida = '" + sdf.format(date) + "'");
        query.append(" order by id");
        Query result = session.createQuery(query.toString());
        List<Saida> registrosTmp = result.list();
        List<Saida> registros = new ArrayList(registrosTmp);
        session.flush();
        session.close();
        return registros;
    }

    public Saida obterSaidaById(int id) {
        Session session = sessionFactory.openSession();
        Saida saida = (Saida) session.load(Saida.class, id);
        Saida entradaDto = mountDtoSaida(saida);
        session.flush();
        session.close();
        return entradaDto;
    }

    private Saida mountDtoSaida(Saida saida) {
        return new Saida(saida.getId(),
                saida.getUsuario(),
                saida.getTiposMovimento(),
                saida.getRua(),
                null,
                saida.getProduto(),
                saida.getPalete(),
                saida.getQuantidade(),
                null,
                saida.getDataSaida(),
                saida.isCancelado());
    }

    public void cancelarSaida(Saida saida) throws ReincidenciaException,EspacoExcedidoPaleteException, EspacoExcedidoRuaException, Exception {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        saida = (Saida) session.load(Saida.class, saida.getId());
        if(saida.isCancelado()){
            session.close();
            throw new ReincidenciaException("Registro já cancelado anteriormente");
        }
        saida.setCancelado(true);
        //Checa espaço no palete
        List<Enderecamento> listEnderecamento = session.createCriteria(Enderecamento.class).add(Restrictions.eq("palete.id", saida.getPalete().getId())).list();
        double cubagemOcupada = 0;
        for (int i = 0; i < listEnderecamento.size(); i++) {
            Produto produto = listEnderecamento.get(i).getProduto();
            int qtde = listEnderecamento.get(i).getQuantidade();
            BigDecimal produtoAltura = produto.getAltura().divide(BigDecimal.valueOf(100));
            BigDecimal produtoComprimento = produto.getComprimento().divide(BigDecimal.valueOf(100));
            BigDecimal produtoLargura = produto.getLargura().divide(BigDecimal.valueOf(100));
            BigDecimal subTotal = (produtoAltura.multiply(produtoComprimento).multiply(produtoLargura));
            cubagemOcupada += subTotal.doubleValue() * qtde;
        }
        //Checa espaço que será ocupado
        Produto produto = saida.getProduto();
        BigDecimal produtoAltura = produto.getAltura().divide(BigDecimal.valueOf(100));
        BigDecimal produtoComprimento = produto.getComprimento().divide(BigDecimal.valueOf(100));
        BigDecimal produtoLargura = produto.getLargura().divide(BigDecimal.valueOf(100));
        BigDecimal subTotal = (produtoAltura.multiply(produtoComprimento).multiply(produtoLargura));
        int qtde = saida.getQuantidade();
        cubagemOcupada += subTotal.doubleValue() * qtde;

        if (cubagemOcupada > saida.getPalete().getCubagem()) {
            tx.rollback();
            session.close();
            throw new EspacoExcedidoPaleteException("Não há espaço físico no Palete para estorno do saldo");
        } else {
            Enderecamento enderecamento = new Enderecamento();
            enderecamento.setPalete(saida.getPalete());
            enderecamento.setProduto(saida.getProduto());
            enderecamento.setQuantidade(saida.getQuantidade());
            Enderecamento obj = (Enderecamento) session.createCriteria(Enderecamento.class).add(Restrictions.eq("produto.id", saida.getProduto().getId())).add(Restrictions.eq("palete.id", saida.getPalete().getId())).uniqueResult();
            if (obj != null) {
                int novaQuantidade = obj.getQuantidade() + enderecamento.getQuantidade();
                enderecamento = obj;
                enderecamento.setQuantidade(novaQuantidade);
            }

            Integer qtdePaletes = new RuaDAO().obterQtdePaleteByRua(saida.getPalete().getRua());
            Boolean checkPalete = checkPaleteByRua(enderecamento.getPalete().getId(), enderecamento.getPalete().getRua().getId());
            Integer qtdeMaximo = enderecamento.getPalete().getRua().getQuantidadePaletes();
            if ((qtdePaletes > qtdeMaximo && checkPalete) || (qtdePaletes >= qtdeMaximo && checkPalete == false)) {
                tx.rollback();
                session.close();
                throw new EspacoExcedidoRuaException("A quantidade de paletes excede a quantidade máxima permitida para esta Rua.");
            } else {
                //Atualizar rua do Palete
                session.update(enderecamento.getPalete());

                saida.setCancelado(true);
                session.update(saida);
                //Atualizar ou registrar endereçamento
                if (enderecamento.getId() > 0) {
                    session.update(enderecamento);
                } else {
                    session.save(enderecamento);
                }
            }
        }
        session.flush();
        tx.commit();
        session.close();
    }
}
